package com.apobates.jforum.grief.aurl.executor;

import com.apobates.jforum.grief.aurl.core.AUrlMeta;
import com.apobates.jforum.grief.aurl.core.AUrlMetaBuilder;
import com.apobates.jforum.grief.aurl.spring.StringUtils;
import dev.failsafe.Failsafe;
import dev.failsafe.RetryPolicy;
import dev.failsafe.RetryPolicyBuilder;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * 支持重试的HttpClient执行器
 */
public class HClientRetryExecutor extends HClientExecutor{
    public static PolicyConfig defaultPolicy = PolicyConfig
            .retries(3)
            .delay(Duration.ofSeconds(3))
            .duration(Duration.ofSeconds(11));

    public HClientRetryExecutor(AUrlMeta rawMeta) {
        super(rawMeta);
    }

    @Override
    public Set<HClientResponse<String>> getActionRecords() {
        return Collections.emptySet();
    }

    @Override
    public AUrlMetaBuilder getBuilder() {
        return getRawMeta().toBuilder();
    }

    @Override
    public HClientResponse<Void> head() throws URISyntaxException {
        return head(getRawMeta().toString(), HClientContext.defaultConfig, defaultPolicy);
    }

    /**
     * 执行常规的HEAD请求
     * @param url 请求的地址
     * @param config 请求上下文配置
     * @param policy 重试策略
     * @return
     * @throws URISyntaxException
     */
    public static HClientResponse<Void> head(String url, HClientContext<String> config, PolicyConfig policy) throws URISyntaxException{
        if(!StringUtils.hasLength(url)){
            throw new IllegalArgumentException("请求地址不可用");
        }
        HttpClient client = buildClient(config);
        HttpRequest request = HttpRequest.newBuilder()
                .method("HEAD", HttpRequest.BodyPublishers.noBody())
                .uri(new URI(contactQueryString(url, config.getQueryString()))) // config.queryString
                .timeout(config.getRequestTimeOut()) // config.requestTimeOut
                .build();
        try {
            return Failsafe.with(covertPolicy(policy)).get(() -> {
                HttpResponse<Void> response = client.send(request, HttpResponse.BodyHandlers.discarding());
                return HClientResponse.success(response.statusCode(), null);
            });
        }catch (Exception e){
            return config.getExceptionHander().apply(e);
        }
    }

    private static RetryPolicy<Object> covertPolicy(PolicyConfig policy){
        RetryPolicyBuilder<Object> builder = RetryPolicy.builder()
                .handle(TimeoutException.class, ExecutionException.class, IOException.class, InterruptedException.class)
                .withDelay(policy.delay)
                .withMaxRetries(policy.retries)
                .withMaxDuration(policy.duration)
                .withJitter(policy.jitter)
                .onFailedAttempt(e -> System.out.println("Connection attempt failed:" + e.getAttemptCount()))
                .onRetry(e -> System.out.println(String.format("Failure %s. Retrying.", e.getAttemptCount())));
        if(policy.abortException != null){
            builder = builder.abortOn(policy.abortException);
        }
        return builder.build();
    }

    @Override
    public HClientResponse<String> get() throws URISyntaxException {
        return get(getRawMeta().toString(), HClientContext.defaultConfig, defaultPolicy);
    }

    /**
     * 执行常规的GET请求
     * @param url 请求的地址
     * @param config 请求上下文配置
     * @param policy 重试策略
     * @return
     * @param <T>
     */
    public static <T> HClientResponse<T> get(String url, HClientContext<T> config, PolicyConfig policy) throws URISyntaxException {
        try {
            return execute("GET", url, config, HttpRequest.BodyPublishers.noBody(), policy);
        }catch (Exception e){
            return config.getExceptionHander().apply(e);
        }
    }

    /**
     * 使用请求客户端执行指定的请求
     * @param method 请求的方法
     * @param url 请求的地址
     * @param config 请求上下文配置
     * @param bodyPublisher 请求的内容
     * @param policy 重试策略
     * @return
     * @param <T>
     * @throws URISyntaxException
     **/
    public static <T> HClientResponse<T> execute(String method, String url, HClientContext<T> config, HttpRequest.BodyPublisher bodyPublisher, PolicyConfig policy) throws URISyntaxException {
        if(!StringUtils.hasLength(url)){
            throw new IllegalArgumentException("[url]请求地址不可用");
        }
        String[] allowMethod = new String[]{"GET", "POST", "HEAD", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"};
        if(!List.of(allowMethod).contains(method)){
            throw new IllegalArgumentException("[method]非法的请求方法");
        }
        HttpClient client = buildClient(config);
        HttpRequest.Builder request = HttpRequest.newBuilder()
                .method(method, bodyPublisher)
                .uri(new URI(contactQueryString(url, config.getQueryString()))) // config.queryString
                .timeout(config.getRequestTimeOut()) // config.requestTimeOut
                .header("Content-Type", config.getContentType()); // config.contentType
        if(config.getHeaders() != null && config.getHeaders().length > 0){
            request = request.headers(config.getHeaders());
        }
        return executeRetry(client, request.build(), config.isAsync(), config.getResponseTimeOut(), config.getHandler(), policy);
    }

    /**
     * 使用请求客户端执行指定的请求
     * @param client 请求客户端
     * @param request 请求
     * @param isAsyncRequest 是否是异步请求,true异步/false同步
     * @param responseTimeOut 响应等待的时长
     * @param responseBodyHandler 响应内容处理句柄
     * @param policy 重试策略
     * @return
     * @param <T>
     */
    private static <T> HClientResponse<T> executeRetry(
            HttpClient client,
            HttpRequest request,
            boolean isAsyncRequest,
            Duration responseTimeOut,
            HttpResponse.BodyHandler<T> responseBodyHandler,
            PolicyConfig policy){
        return Failsafe.with(covertPolicy(policy)).get(() -> {
            if(!isAsyncRequest) {
                HttpResponse<T> res = client.send(request, responseBodyHandler);
                return HClientResponse.success(res.statusCode(), res.body());
            }else {
                CompletableFuture<HttpResponse<T>> res = client.sendAsync(request, responseBodyHandler);
                return res.thenApply(ele -> HClientResponse.success(ele.statusCode(), ele.body())).get(responseTimeOut.getSeconds(), TimeUnit.SECONDS); // .get(5, TimeUnit.SECONDS);
            }
        });
    }

    @Override
    public HClientResponse<String> getJson() throws URISyntaxException {
        return getJson(getRawMeta().toString(), defaultPolicy);
    }

    /**
     * 执行常规同步GET JSON请求
     *
     * @param url 请求url
     * @param policy 重试策略
     * @return
     */
    public static HClientResponse<String> getJson(String url, PolicyConfig policy) throws URISyntaxException {
        HClientContext<String> HClientContext = com.apobates.jforum.grief.aurl.executor.HClientContext.Builder.<String>of(1).contentType("application/json").bodyHandler(HttpResponse.BodyHandlers.ofString()).build();
        return get(url, HClientContext, policy);
    }

    @Override
    public HClientResponse<String> post(Map<String, String> postForm) throws URISyntaxException {
        return post(getRawMeta().toString(), postForm, defaultPolicy);
    }

    /**
     * 执行常规同步Form POST请求
     *
     * @param url 请求url
     * @param postForm 表单值
     * @param policy 重试策略
     * @return
     * @throws URISyntaxException
     */
    public static HClientResponse<String> post(String url, Map<String, String> postForm, PolicyConfig policy) throws URISyntaxException {
        HClientContext<String> HClientContext = com.apobates.jforum.grief.aurl.executor.HClientContext.Builder.<String>of(1).contentType("application/x-www-form-urlencoded").bodyHandler(HttpResponse.BodyHandlers.ofString()).build();
        return post(url, HClientContext, HttpRequest.BodyPublishers.ofString(getFormDataAsString(postForm, StandardCharsets.UTF_8)), policy);
    }

    /**
     * 执行常规的POST请求
     * @param url 请求的地址
     * @param config 请求上下文配置
     * @param bodyPublisher 发送的内容
     * @param policy 重试策略
     * @return
     * @param <T>
     */
    public static <T> HClientResponse<T> post(String url, HClientContext<T> config, HttpRequest.BodyPublisher bodyPublisher, PolicyConfig policy) throws URISyntaxException {
        return execute("POST", url, config, bodyPublisher, policy);
    }

    /**
     * 重试策略配置
     */
    public static class PolicyConfig{
        // 最大偿试次数
        private int retries = 3;
        // 每次偿试之间的间隔
        private Duration delay = Duration.ofSeconds(1);
        // 最长持续时间
        private Duration duration = Duration.ofMinutes(1);
        // 抖动
        private Duration jitter = Duration.ofMillis(100);
        // 中止
        private Class<? extends Throwable>[] abortException;

        private PolicyConfig(){}

        /**
         * 设置：最大偿试次数, 默认为:3
         * @param retries
         * @return
         */
        public static PolicyConfig retries(int retries){
            PolicyConfig ins = new PolicyConfig();
            ins.retries = retries;
            return ins;
        }

        /**
         * 设置：每次偿试之间的间隔, 默认为:1秒
         * @param delay
         * @return
         */
        public PolicyConfig delay(Duration delay){
            this.delay = delay;
            return this;
        }

        /**
         * 设置: 最长持续时间, 默认为:1分钟
         * 注意: 如果尚未达到最大尝试次数，重试将停止。
         * @param duration
         * @return
         */
        public PolicyConfig duration(Duration duration){
            this.duration = duration;
            return this;
        }

        /**
         * 设置: 抖动, 默认为:100毫秒
         * 注意: 基于时间的抖动：
         * @param jitter
         * @return
         */
        public PolicyConfig jitter(Duration jitter){
            this.jitter = jitter;
            return this;
        }

        /**
         * 设置: 中止重试的结果、异常或条件
         * @param exception
         * @return
         */
        public PolicyConfig abort(Class<? extends Throwable>... exception){
            this.abortException = exception;
            return this;
        }
    }
}
